using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using Castle.DynamicProxy;

using JW.Framework.IOC.Attributes;
using JW.Framework.IOC.Delegates;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace JW.Framework.IOC
{
    public static partial class Extensions
    {
        // 用来缓存构建类型的工厂
        private static Dictionary<Type, List<InjectPropertyValue>> _propertyFactoryCaches = new Dictionary<Type, List<InjectPropertyValue>>();
        // 用来缓存类型上面配置的拦截器
        private static Dictionary<Type, InjectInterceptor[]> _interceptorFactoryCaches = new Dictionary<Type, InjectInterceptor[]>();

        /// <summary>
        /// 自动注册某个程序集下由 AutoDependencyInjectAttribute 修饰的类，如果该类实现了某个接口，则该实现类的命名空间必须以接口的命名空间开头或在AutoDependencyInjectAttribute中指定
        /// </summary>
        /// <param name="services">服务注册容器</param>
        /// <param name="assemblyName">注册的程序集名称</param>
        /// <returns>服务注册容器</returns>
        public static IServiceCollection RegisterAssembly(this IServiceCollection services, string assemblyName)
        {
            Assembly assembly = Assembly.Load(assemblyName); // 获取程序集
            return RegisterAssembly(services, assembly);
        }

        public static IServiceCollection RegisterAssembly(this IServiceCollection services, Assembly assembly)
        {
            Type autoDependencyInjectAttributeType = typeof(AutoInjectAttribute);
            IEnumerable<Type> types = assembly.GetTypes().Where(type => type.IsDefined(autoDependencyInjectAttributeType, false));

            foreach (Type injectType in types)
            {
                AutoInjectAttribute autoDependencyInject = injectType.GetCustomAttribute<AutoInjectAttribute>();
                Type implementType = autoDependencyInject.ImplementType;

                if (implementType == null && string.IsNullOrWhiteSpace(autoDependencyInject.StrImplementType) == false)
                {
                    string[] implementTypeParts = autoDependencyInject.StrImplementType.Split(",", StringSplitOptions.RemoveEmptyEntries);
                    if(implementTypeParts != null && implementTypeParts.Length == 2)
                    {
                        Assembly implementAssembly = Assembly.Load(implementTypeParts[1].Trim()); // 载入程序集
                        implementType = implementAssembly.GetType(implementTypeParts[0].Trim()); // 获取类型
                    }
                }

                if (implementType == null)
                {
                    implementType = injectType.Assembly.GetTypes().FirstOrDefault(t => t.Namespace.StartsWith(injectType.Namespace) && injectType.IsAssignableFrom(t));
                }

                if (implementType == null)
                {
                    if(injectType.IsClass && injectType.IsAbstract == false) implementType = injectType;
                    else
                        continue;
                }

                ServiceLifetime lifetime = autoDependencyInject.Lifetime;

                services.TryAdd(new ServiceDescriptor(implementType, implementType, lifetime));
                if (implementType != default && injectType != implementType && injectType.IsAssignableFrom(implementType)) // 只有注入类型和实现类型不一样时，才能创建代理实现属性的注入及配置注入，以及使用aop拦截器
                {
                    RegisterPropertyFactoryCache(injectType, implementType);
                    RegisterInterceptorFactoryCache(services, injectType);
                    RegisterTypeWithFactoryCache(services, injectType, implementType, lifetime);
                }
            }

            return services;
        }

        private static void RegisterPropertyFactoryCache(Type injectType, Type implementType)
        {
            if (_propertyFactoryCaches.ContainsKey(injectType)) return;

            lock (injectType)
            {
                if (_propertyFactoryCaches.ContainsKey(injectType)) return;
                List<InjectPropertyValue> setPropertyValues = new List<InjectPropertyValue>();

                Type autowiredAttributeType = typeof(AutowiredAttribute);
                Type valueAttributeType = typeof(ValueAttribute);

                IEnumerable<PropertyInfo> properties = implementType.GetTypeInfo().DeclaredProperties.Where(p => p.CanWrite);
                foreach (PropertyInfo info in properties)
                {
                    // 属性注入
                    if (info.IsDefined(autowiredAttributeType, false) && info.GetCustomAttribute<AutowiredAttribute>() is AutowiredAttribute autowiredAttribute)
                    {
                        InjectPropertyValue setPropertyValue = (provider, configuration, instance) =>
                        {
                            object impl = provider.GetService(autowiredAttribute.InjectType ?? info.PropertyType);
                            if (impl != null) info.SetValue(instance, impl);
                            return instance;
                        };

                        setPropertyValues.Add(setPropertyValue);
                    }
                    // 配置值注入
                    else if (info.IsDefined(valueAttributeType, false) && info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)
                    {
                        InjectPropertyValue setPropertyValue = (provider, configuration, instance) =>
                        {
                            object objectValue = valueAttribute.DefaultValue;
                            IConfigurationSection section = configuration.GetSection(valueAttribute.Key);
                            if (section != null)
                            {
                                objectValue = section.Get(info.PropertyType) ?? objectValue;
                            }

                            if (objectValue != null)
                            {
                                info.SetValue(instance, objectValue);
                            }
                            return instance;
                        };

                        setPropertyValues.Add(setPropertyValue);
                    }
                }

                if (setPropertyValues.Count > 0) _propertyFactoryCaches.TryAdd(injectType, setPropertyValues);
                else _propertyFactoryCaches.TryAdd(injectType, default);
            }
        }

        private static void RegisterInterceptorFactoryCache(IServiceCollection services, Type injectType)
        {
            if (_interceptorFactoryCaches.ContainsKey(injectType)) return;

            lock (injectType)
            {
                if (_interceptorFactoryCaches.ContainsKey(injectType)) return;
                Type interceptorAttributeType = typeof(InterceptorAttribute);
                List<InjectInterceptor> setInterceptors = new List<InjectInterceptor>();
                if (injectType.IsDefined(interceptorAttributeType, false) && injectType.GetCustomAttribute<InterceptorAttribute>() is InterceptorAttribute interceptorAttribute && interceptorAttribute.HasInterceptor)
                {
                    for (int index = 0; index < interceptorAttribute.InterceptorTypes.Length; index++)
                    {
                        Type interceptorType = interceptorAttribute.InterceptorTypes[index];
                        services.TryAdd(new ServiceDescriptor(interceptorType,interceptorType, ServiceLifetime.Transient));
                        InjectInterceptor setInterceptor = (provider) => provider.GetRequiredService(interceptorType) as IInterceptor;
                        setInterceptors.Add(setInterceptor);
                    }
                }

                if (setInterceptors.Count > 0) _interceptorFactoryCaches.Add(injectType, setInterceptors.ToArray());
                else _interceptorFactoryCaches.Add(injectType, default);
            }
        }

        private static void RegisterTypeWithFactoryCache(IServiceCollection services, Type injectType, Type implementType, ServiceLifetime lifetime)
        {
            object Factory(IServiceProvider provider)
            {
                object target = provider.GetService(implementType);
                IConfiguration configService = provider.GetService(typeof(IConfiguration)) as IConfiguration;

                if (_propertyFactoryCaches.TryGetValue(injectType, out List<InjectPropertyValue> setPropertyValues) && setPropertyValues != default)
                {
                    foreach (var setPropertyValue in setPropertyValues)
                    {
                        setPropertyValue.Invoke(provider, configService, target);
                    }
                }

                //IInterceptor[] interceptors = null;
                if (_interceptorFactoryCaches.TryGetValue(injectType, out InjectInterceptor[] setInterceptors) && setInterceptors != default)
                {
                    IInterceptor[] interceptors = new IInterceptor[setInterceptors.Length];
                    for (int index = 0; index < setInterceptors.Length; index++)
                    {
                        interceptors[index] = setInterceptors[index].Invoke(provider);
                    }

                    IProxyGenerator proxyGenerator = new ProxyGenerator();
                    object proxyInstance = proxyGenerator.CreateInterfaceProxyWithTarget(injectType, target, interceptors);
                    return proxyInstance;
                }
                else
                {
                    //interceptors = Array.Empty<IInterceptor>();
                    return target;
                }

                //IProxyGenerator proxyGenerator = new ProxyGenerator();
                //object proxyInstance = proxyGenerator.CreateInterfaceProxyWithTarget(injectType, target, interceptors);
                //return proxyInstance;
            }
            services.TryAdd(new ServiceDescriptor(injectType, Factory, lifetime));
        }
    }
}
